#Copyright 2007 New Medio.  This file is part of ActiveShipping.  See README for license and legal information.

module ActiveShipping
	class UpsGateway < Base
		attr_accessor :username, :ups_account, :ups_license, :ups_pickup_type, :debug_mode, :test_mode

		#:username => "USERNAME|ACCOUNT_NUMBER|LICENSE_NUMBER|UPS_PICKUP_TYPE", :password => "password", :origin_postal_code, :origin_country, :package_type => "02"
		#:ups_pickup_type => 01 (daily), 03 (customer counter), 06 (one time pickup), 07 (on call air pickup), 11 (retail rates / UPS Store), 19 (letter center), 20 (air service center)
		#:ups_account => Your UPS Account Number
		#:ups_license => Your UPS License Number
		#:ups_pickup_type => How UPS Gets Your Package
		def initialize(options = {})
			if(options[:dsn])
				options = options.merge(Util.parse_dsn(options.delete(:dsn)))
			end
			@username = options[:username]
			@ups_account = options[:ups_account]
			@ups_license = options[:ups_license]
			@ups_pickup_type = options[:ups_pickup_type]
			@ups_package_type = "02" #Generic "package"
			@password = options[:password]
			@debug_mode = options[:debug_mode] || false
			@test_mode = options[:test_mode] || false
		end

		def self.initializer_properties
			[ ["Account Number", :ups_account, :text], ["License Number", :ups_license, :text], ["Pickup Type", :ups_pickup_type, :select] ]
		end

		def self.initializer_select_options(param)
			case param
				when :ups_pickup_type
					[ ["Daily", "01"], ["Customer Counter", "03"], ["One Time Pickup", "06"], ["On Call Air Pickup", "07"], ["Retail Rates / UPS Store", "11"], ["Letter Center", "19"], ["Air Service Center", "20"] ]
				else
					nil
			end
		end

		def self.requires_username?
			return true
		end

		def self.requires_password?
			return true
		end

		def available_shipping_methods
			self.class.available_shipping_methods
		end

		def self.available_shipping_methods
			[:ground, :priority, :express]
		end

		def carrier_code
			self.class.carrier_code
		end

		def self.carrier_code
			return :ups
		end

		def self.carrier_name
			return "UPS"
		end

		def estimate_shipping_cost(shipping_method, origin, destination, weight, num_items = nil, value = nil, other_info = nil)
			return calculate_weighted_shipping(shipping_method, weight, origin, destination)
		end

		private
		def access_request
			"<?xml version='1.0' encoding='utf-8' ?>
<AccessRequest xml:lang='en-US'>
<AccessLicenseNumber>#{@ups_license}</AccessLicenseNumber>
<UserId>#{@username}</UserId>
<Password>#{@password}</Password>
</AccessRequest>
"
		end

		def self.gateway_url
			"https://wwwcie.ups.com/ups.app/xml/Rate"
		end

		def calculate_weighted_shipping(method, weight, origin, destination)
			request_xml = access_request + xpci_request(method, weight, origin, destination)

			result = ActiveShipping::Util.run_http_post_connection(UpsGateway.gateway_url, request_xml).body

			if @debug_mode
				puts "Result is #{result}"
			end

			if(result.match(/<ResponseStatusCode>\s*(\d+)\s*</s))
				status_code = $1
			else
				raise "Could not parse result for status:#{result}:"
			end

			if(result.match(/<ResponseStatusDescription>\s*(.*?)\s*</s))
				status_description = $1
			else
				raise "Could not parse result for status description:#{result}:"
			end

			if(result.match(/<RatedShipment>.*?<TotalCharges>\s*<CurrencyCode>\s*USD\s*<.CurrencyCode>\s*<MonetaryValue>\s*([\d\.]+)\s*</s))
				cost = $1
			else
				raise "Could not parse result for cost:#{result}:"
			end

			if status_code != "1"
				raise "Error:#{status}:#{status_description}:#{cost}"
			end

			if cost == 0
				raise "Error:#{status}:#{status_description}:#{cost}"
			end

			return BigDecimal.new(cost)
		end

		def xpci_request(method, weight, origin, destination)
			service_number = @@service_types[method]

			request = "<?xml version='1.0' encoding='utf-8' ?>
<RatingServiceSelectionRequest xml:lang='en-US'>
<Request>
<TransactionReference>
<CustomerContext>Bare Bones Rate Request</CustomerContext>
<XpciVersion>1.0</XpciVersion>
</TransactionReference>
<RequestAction>Rate</RequestAction>
<RequestOption>Rate</RequestOption>
</Request>
<PickupType>
<Code>#{@ups_pickup_type}</Code>
</PickupType>
<Shipment>
<Shipper>
<Address>
<PostalCode>#{origin.postal_code}</PostalCode>
<CountryCode>#{origin.country}</CountryCode>
</Address>
</Shipper>
<ShipTo>
<Address>
<PostalCode>#{destination.postal_code}</PostalCode>
<CountryCode>#{destination.country}</CountryCode>
</Address>
</ShipTo>
<ShipFrom>
<Address>
<PostalCode>#{origin.postal_code}</PostalCode>
<CountryCode>#{origin.country}</CountryCode>
</Address>
</ShipFrom>
<Service>
<Code>#{service_number}</Code>
</Service>
<Package>
<PackagingType>
<Code>#{@ups_package_type}</Code>
</PackagingType>
<PackageWeight>
<UnitOfMeasurement>
<Code>LBS</Code>
</UnitOfMeasurement>
<Weight>#{weight}</Weight>
</PackageWeight>
</Package>
</Shipment>
</RatingServiceSelectionRequest>
"
		end

		@@service_types = {
			:ground => "03", #UPS Ground
			:priority => "12", #UPS 3-Day Select
			:express => "01" #UPS Next Day Air
		}
	end
end
ActiveShipping::Base.register_shipping_module(ActiveShipping::UpsGateway)
